package stack;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import stack.StackWorker.WorkerInfo;

public class Elimination {

	CentralStack stack_;
	AtomicWorkerInfo[] location_;
	AtomicInteger[] collision_;
	int workers_num_;
	static final int EMPTY = -1;
	
	enum direction {
		SHRINK, ENLARGE
	}

	public Elimination(CentralStack stack, int workers_num) {
		stack_ = stack;
		workers_num_=workers_num;
		location_ = new AtomicWorkerInfo[workers_num_];
		collision_ = new AtomicInteger[workers_num_];
		for (int i = 0; i < location_.length; i++) {
			location_[i] = new AtomicWorkerInfo(null);
		}
		for (int i = 0; i < collision_.length; i++) {
			collision_[i] = new AtomicInteger(EMPTY);
		}
	}

	public void lesOp(WorkerInfo workerInfo) {
		WorkerInfo otherInfo = null;
		int position, otherPos;
		while (true) {
			location_[workerInfo.id_].setWorker(workerInfo);
			position = getPosition(workerInfo);
			otherPos = collision_[position].get();
			while (!collision_[position].compareAndSet(otherPos, workerInfo.id_)) {
				otherPos = collision_[position].get();
			}
			if (otherPos != EMPTY) {
				otherInfo = location_[otherPos].getWorker();
				if (otherInfo != null && otherInfo.id_ == otherPos&& otherInfo.op_ != workerInfo.op_) {
					if (location_[workerInfo.id_].compareAndSet(workerInfo, null)) {
						if (tryCollision(workerInfo, otherInfo) == true) {
							return;
						} else {
							if (stack_.tryPerformStackOp(workerInfo) == true) {
								return;
							}
						}
					} else {
 						finishCollision(workerInfo);
						return;
					}
				}
			}
			
			delay(workerInfo);
			adaptWidth(workerInfo, direction.SHRINK);
			if (!(location_[workerInfo.id_].compareAndSet(workerInfo, null))){
				finishCollision(workerInfo);
				return;
			}
			if (stack_.tryPerformStackOp(workerInfo) == true) {
				return;
			}
		}

	}

	private void adaptWidth(WorkerInfo workerInfo, direction direct) {
		if (direct == direction.SHRINK){
			if(workerInfo.adaptCount_>0){
				workerInfo.adaptCount_ --;
			}
			else {
				workerInfo.adaptCount_ = StackWorker.ADAPT_INIT;
				workerInfo.adaptFactor_ = Math.max(workerInfo.adaptFactor_/2, StackWorker.MIN_FACTOR);
			}
		}
		if (direct == direction.ENLARGE)
		{
			if(workerInfo.adaptCount_<StackWorker.MAX_COUNT){
				workerInfo.adaptCount_ ++;
			}
			else {
				workerInfo.adaptCount_ = StackWorker.ADAPT_INIT;
				workerInfo.adaptFactor_ = Math.min(workerInfo.adaptFactor_*2, StackWorker.MAX_FACTOR);
			}
		}
		
	}

	private void delay(WorkerInfo workerInfo) {
		try {
			Thread.sleep(workerInfo.spin_);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		workerInfo.spin_*=2;
	}

	private void finishCollision(WorkerInfo workerInfo) {

		if(workerInfo.op_== StackWorker.POP){
			workerInfo.cell_=location_[workerInfo.id_].getWorker().cell_;
			workerInfo.logPop(System.currentTimeMillis(), this.getClass().getName());
			location_[workerInfo.id_].setWorker(null);
		}
		if(workerInfo.op_== StackWorker.PUSH){
			workerInfo.logPush(System.currentTimeMillis(), this.getClass().getName());
			
		}
	}

	private boolean tryCollision(WorkerInfo workerInfo, WorkerInfo otherInfo) {

		if (workerInfo.op_ == StackWorker.PUSH) {
			if (location_[otherInfo.id_].compareAndSet(otherInfo, workerInfo)) {
				workerInfo.logPush(System.currentTimeMillis(), this.getClass().getName());
				return true;
			} else {
				adaptWidth(workerInfo, direction.ENLARGE);
				return false;
			}
		}
		if (workerInfo.op_ == StackWorker.POP) {
			if (location_[otherInfo.id_].compareAndSet(otherInfo, null)) {
				workerInfo.cell_ = otherInfo.cell_;
				workerInfo.logPop(System.currentTimeMillis(), this.getClass().getName());
				location_[workerInfo.id_].setWorker(null);
				return true;
			} else {
				adaptWidth(workerInfo, direction.ENLARGE);
				return false;
			}

		}
		return false;

	}

	private int getPosition(WorkerInfo workerInfo) {
	
		return (int) (Math.random() * (workerInfo.adaptFactor_*workers_num_)); 
				
	}

	private class AtomicWorkerInfo {

		AtomicReference<WorkerInfo> worker_info_;

		public AtomicWorkerInfo(WorkerInfo worker_info) {
			worker_info_=new AtomicReference<WorkerInfo>(worker_info);
		}

		public WorkerInfo getWorker() {

			return worker_info_.get();
		}

		public void setWorker(WorkerInfo worker_info) {
			worker_info_.set(worker_info);

		}

		public boolean compareAndSet(WorkerInfo worker, WorkerInfo other) {

			return worker_info_.compareAndSet(worker, other);
		}







	}

}


